#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <pwd.h>
#include <wait.h>
#include <sys/types.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <ctype.h>

#define L_GREEN "\e[1;32m"
#define L_BLUE  "\e[1;34m"
#define L_RED   "\e[1;31m"
#define L_YELLOW "\e[1;33m"
#define L_CYAN  "\e[1;36m"
#define L_PURPLE "\e[1;35m"
#define WHITE   "\e[0m"

#define MAX_ARGS 100
#define TRUE 1
#define FALSE 0

// primary functions
void set_prompt();
char parse_command();
void deal_inner_command();
void deal_external_command();
void deal_pipe_command();
// subsidiary functions
void print_help();
char* strtrim(char* str);

char prompt[BUFSIZ];
char* line = NULL;
char** argv;
int argc = 0;
char* Inner[] = {
    "exit","help","cd"
};
int inner_nums = 3;

int main() {
    while (TRUE)
    {
        set_prompt();
        
        if (line != NULL) {
            free(line);
            line = NULL;
        }
        
        if (!(line = readline(prompt)))
            break;
        
        line = strtrim(line);
        if (strlen(line) == 0)
            continue;
        add_history(line);

        switch (parse_command())
        {
        case 'I':
            deal_inner_command();
            break;
        case 'E':
            deal_external_command();
            break;
        case 'P':
            deal_pipe_command();
            break;
        default:
            printf("Error: Unknown command!\n");
            break;
        }

        for (int i = 0; i < argc; ++i) {
            free(argv[i]);
        }
        free(argv);
    }
    
    if (line != NULL) {
        free(line);
        line = NULL;
    }

    return 0;
}
// primary functions
void deal_pipe_command() {
    char *cmd1[MAX_ARGS], *cmd2[MAX_ARGS];
    int cmd1_count = 0, cmd2_count = 0;
    char *token, *saveptr1, *saveptr2;
    char *line_cpy = strdup(line);
    int pipefd[2];

    // First, split by '|'
    token = strtok_r(line_cpy, "|", &saveptr1);
    if (token != NULL) {
        // Parse the first command and its arguments
        char *cmd_token = strtok_r(token, " \t\n\r\a", &saveptr2);
        while (cmd_token != NULL && cmd1_count < MAX_ARGS - 1) {
            cmd1[cmd1_count++] = cmd_token;
            cmd_token = strtok_r(NULL, " \t\n\r\a", &saveptr2);
        }
        cmd1[cmd1_count] = NULL;

        // Get the second part
        token = strtok_r(NULL, "|", &saveptr1);
        if (token != NULL) {
            // Remove leading whitespace from the second command string
            while (*token == ' ' || *token == '\t') token++;

            // Parse the second command and its arguments
            cmd_token = strtok_r(token, " \t\n\r\a", &saveptr2);
            while (cmd_token != NULL && cmd2_count < MAX_ARGS - 1) {
                cmd2[cmd2_count++] = cmd_token;
                cmd_token = strtok_r(NULL, " \t\n\r\a", &saveptr2);
            }
            cmd2[cmd2_count] = NULL;
        } else {
            printf("Invalid pipe command.\n");
            free(line_cpy);
            return;
        }
    } else {
        printf("Invalid pipe command.\n");
        free(line_cpy);
        return;
    }

    // create pipe
    if (pipe(pipefd) == -1) {
        perror("Pipe Failed");
        return;
    }

    // child process for the first command
    if (fork() == 0) {
        close(pipefd[0]); // close read edge
        dup2(pipefd[1], STDOUT_FILENO); // dup the stdout
        close(pipefd[1]); // close extra write edge
        execvp(cmd1[0], cmd1);
        perror("Command 1 failed");
        exit(EXIT_FAILURE);
    }

    // child process for the second command
    if (fork() == 0) {
        close(pipefd[1]);
        dup2(pipefd[0], STDIN_FILENO); // dup the stdin
        close(pipefd[0]);
        execvp(cmd2[0], cmd2);
        perror("Command 2 failed");
        exit(EXIT_FAILURE);
    }

    close(pipefd[0]);
    close(pipefd[1]);
    wait(NULL);
    wait(NULL);

    free(line_cpy);
}

void deal_inner_command() {
    if (!(strcmp(argv[0], "exit"))) {
        exit(EXIT_SUCCESS);
    }
    else if (!(strcmp(argv[0], "help"))) {
        print_help();
    }
    else if (!(strcmp(argv[0], "cd"))) {
        char *new_dir;
        if (argc > 1 && strcmp(argv[1], "~")) {
            new_dir = argv[1];
        } else { // home
            struct passwd *pw = getpwuid(getuid());
            new_dir = pw->pw_dir;
        }

        if (chdir(new_dir) != 0) {
            perror("cd failed");
        }
    }
}

void deal_external_command() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        return;
    } else if (pid == 0) { // child process
        execvp(argv[0], argv);
        // if return -> failed
        perror("Command not found or cannot be executed");
        exit(EXIT_FAILURE);
    } else { // parent process
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS) {
            printf("External command execution failed.\n");
        }
    }
}

char parse_command() {
    char delims[] = " \t\n\r\a";
    char* line_cpy = strdup(line);
    char* token;
    int i = 0;

    if (strchr(line, '|') != NULL) {
        free(line_cpy);
        return 'P'; // pipe command
    }

    token = strtok(line_cpy, delims);
    argv = (char**)malloc(MAX_ARGS * sizeof(char*));
    while (token != NULL && i < MAX_ARGS - 1)
    {
        argv[i++] = strdup(token);
        token = strtok(NULL, delims);
    }
    argv[i] = NULL;
    argc = i;

    for (int j = 0; j < inner_nums; ++j) {
        if (!(strcmp(argv[0], Inner[j]))) {
            free(line_cpy);
            return 'I'; // inner command
        }
    }

    free(line_cpy);
    return 'E'; // external command
}

void set_prompt() {
    char hostname[100];
	char cwd[100];
    char delims[] = "/";
	char type = '#';
    struct passwd* pw;

    if (gethostname(hostname,sizeof(hostname)) == -1) {
		//get hostname failed
		strcpy(hostname,"unknown");
	}
    pw = getpwuid(getuid());
    if (!(getcwd(cwd,sizeof(cwd)))) {
		//get cwd failed
		strcpy(cwd,"unknown");	
	}

    char cwdcopy[100];
    strcpy(cwdcopy,cwd);
    char *first = strtok(cwdcopy,delims);
	char *second = strtok(NULL,delims);
    //if at home 
	if (!(strcmp(first,"home")) && !(strcmp(second,pw->pw_name))) {
		int offset = strlen(first) + strlen(second)+2;
		char newcwd[100];
		char *p = cwd;
		char *q = newcwd;

		p += offset;
		while(*(q++) = *(p++));
		char tmp[100];
		strcpy(tmp,"~");
		strcat(tmp,newcwd);
		strcpy(cwd,tmp);
	}

    if(getuid() == 0)//if supera
		type = '#';
	else
		type = '$';
    sprintf(prompt, "\001\e[1;32m\002%s@%s\001\e[0m\002:\001\e[1;33m\002%s\001\e[0m\002%c ",
            pw->pw_name, hostname, cwd, type);
}

// subsidiary functions
void print_help() {
    printf("Inner Commands:\n");
    printf("    [exit]: exit normally.\n");
    printf("    [help]: print this content.\n");
    printf("    [cd]: go to the target directory.\n");
}

char* strtrim(char* str) {
    if (!str) return NULL;
    char* end = str + strlen(str) - 1;
    while (isspace(*str)) str++;
    while (end >= str && isspace(*end)) end--;
    *(end+1) = '\0';
    return str;
}